package de.uni_bremen.exceptions.oops;

import java.util.ArrayList;
import java.util.List;

/**
 * Die Klasse repräsentiert den Syntaxbaum des gesamten Programms. Sie ist der Einstiegspunkt für die Kontextanalyse und
 * die Synthese.
 */
class Program
{
	/** Die benutzerdefinierte Klasse. */
	List<ClassDeclaration>	theClasses	= new ArrayList<ClassDeclaration>();
	
	/**
	 * Konstruktor.
	 * 
	 * @param theClass
	 *            Die benutzerdefinierte Klasse.
	 */
	Program(List<ClassDeclaration> theClasses)
	{
		this.theClasses.addAll(theClasses);
		
		// Vordefinierte Klasse hinzufügen
		this.theClasses.add(ClassDeclaration.objectClass);
		this.theClasses.add(ClassDeclaration.intClass);
		this.theClasses.add(ClassDeclaration.boolClass);
		this.theClasses.add(ClassDeclaration.exceptionClass);
	}
	
	/**
	 * Eine Ausdruck, der ein Objekt der Klasse Main erzeugt und dann darin die Methode main aufruft. Entspricht NEW
	 * Main.main.
	 */
	Expression	main	= new AccessExpression(new NewExpression(new ResolvableIdentifier("Main", null), null),
								new VarOrCall(new ResolvableIdentifier("main", null)));
	
	/**
	 * Die Methode führt die Kontextanalyse für das Programm durch.
	 * 
	 * @throws CompileException
	 *             Während der Kontextanylyse wurde ein Fehler gefunden.
	 */
	void contextAnalysis() throws CompileException
	{
		Declarations declarations = new Declarations();
		
		// Integer enthält ein Element
		ClassDeclaration.intClass.objectSize = ClassDeclaration.HEADERSIZE + 1;
		
		// Boolean enthält ein Element
		ClassDeclaration.boolClass.objectSize = ClassDeclaration.HEADERSIZE + 1;
		
		// Neuen Deklarationsraum schaffen
		declarations.enter();
		
		// Benutzerdefinierte Klassen hinzufügen
		for (ClassDeclaration classdecl : theClasses)
			declarations.add(classdecl);
		
		// Basisklassen auflösen
		for (ClassDeclaration classdecl : theClasses)
		{
			if (classdecl.baseIdent != null)
			{
				declarations.resolveType(classdecl.baseIdent);
				if (((ClassDeclaration) classdecl.baseIdent.declaration).isA(classdecl))
					throw new CompileException("Zyklische Vererbung ist nicht unterstützt",
							classdecl.identifier.position);
				classdecl.baseClass = (ClassDeclaration) classdecl.baseIdent.declaration;
			}
		}
		
		// Methoden-Deklarationen der Klassen durchführen
		for (ClassDeclaration classdecl : theClasses)
			if (classdecl.declarations == null)
				classdecl.contextAnalysis(declarations, true);
		
		ExceptionClassDeclaration.contextAnalysisExceptionCalls(declarations);
		
		// Kontextanalyse für die Methoden der Klassen durchführen
		for (ClassDeclaration classdecl : theClasses)
			classdecl.contextAnalysis(declarations, false);
		
		// Abhängigkeiten für Startup-Code auflösen
		main = main.contextAnalysis(declarations);
		
		// Deklarationsraum verlassen
		declarations.leave();
	}
	
	/**
	 * Die Methode wertet das Programm auf konstante Ausdrücke aus.
	 */
	void evaluateExpressions()
	{
		for (ClassDeclaration classdecl : theClasses)
			for (MethodDeclaration methoddecl : classdecl.methods.keySet())
				for (Statement s : methoddecl.statements)
					s.evaluateExpressions();
	}
	
	/**
	 * Die Methode gibt den Syntaxbaum des Programms aus.
	 * 
	 * @param tree
	 *            Der Strom, in den die Ausgabe erfolgt.
	 */
	void printTree()
	{
		TreeStream tree = new TreeStream(System.out, 4);
		for (ClassDeclaration classdecl : theClasses)
			classdecl.print(tree);
	}
	
	/**
	 * Die Methode generiert den Assembler-Code für das Programm. Sie geht davon aus, dass die Kontextanalyse vorher
	 * erfolgreich abgeschlossen wurde.
	 * 
	 * @param code
	 *            Der Strom, in den die Ausgabe erfolgt.
	 * @param stackSize
	 *            Die größe des Stacks.
	 * @param heapSize
	 *            Die größe des Heaps.
	 */
	void generateCode(CodeStream code, int stackSize, int heapSize)
	{
		// Start-Code: Register initialisieren
		code.setNamespace("_init");
		code.println("MRI R1, 1 ; R1 ist immer 1");
		code.println("MRI R2, _stack ; R2 zeigt auf Stapel");
		code.println("MRI R4, _heap ; R4 zeigt auf die nächste freie Stelle auf dem Heap");
		
		// Ein Objekt der Klasse Main konstruieren und die Methode main aufrufen.
		main.generateCode(code);
		code.println("MRI R0, _end ; Programm beenden");
		
		// Generiere Code für benutzerdefinierte Klasse
		for (ClassDeclaration classdecl : theClasses)
			classdecl.generateCode(code);
		
		// Speicher für Stapel und Heap reservieren
		code.println("_stack: ; Hier fängt der Stapel an");
		code.println("DAT " + stackSize + ", 0");
		code.println("_heap: ; Hier fängt der Heap an");
		code.println("DAT " + heapSize + ", 0");
		code.println("_end: ; Programmende");
	}
}
